package de.jmda.sample.tutorial;

import static de.jmda.core.mproc.ProcessingUtilities.getFields;
import static de.jmda.core.mproc.ProcessingUtilities.getPackageQualifiedName;
import static de.jmda.core.mproc.ProcessingUtilities.getTypeElement;
import static de.jmda.core.util.CollectionsUtil.asSet;
import static de.jmda.core.util.StringUtil.firstLetterToUpperCase;
import static de.jmda.gen.LineIndenter.TAB_SINGLE;
import static de.jmda.gen.java.ModifiersUtil.VisibilityModifier.PRIVATE;
import static de.jmda.gen.java.ModifiersUtil.VisibilityModifier.PUBLIC;
import static de.jmda.gen.java.naming.ImportManagerProvider.getImportManager;
import static de.jmda.gen.java.naming.ImportManagerProvider.newImportManager;
import static de.jmda.gen.java.naming.ImportManagerProvider.restoreImportManager;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

import org.apache.commons.io.FileUtils;
import org.junit.Test;

import de.jmda.core.mproc.task.AbstractTypeElementsTaskTypes;
import de.jmda.core.mproc.task.TaskException;
import de.jmda.core.mproc.task.TaskRunner;
import de.jmda.gen.GeneratorException;
import de.jmda.gen.java.ClassGenerator;
import de.jmda.gen.java.CompilationUnitUtil;
import de.jmda.gen.java.FieldGenerator;
import de.jmda.gen.java.MethodGenerator;
import de.jmda.gen.java.impl.DefaultClassGenerator;
import de.jmda.gen.java.impl.DefaultInstanceFieldGenerator;
import de.jmda.gen.java.impl.DefaultInstanceMethodGenerator;
import de.jmda.gen.java.naming.ImportManager;

/**
 * Demonstrates how to generate a Java Bean style class from a simple Java
 * model.
 */
public class JUTBeanGenerator
{
	// domain model definition - start
	class Person
	{
		String firstname;
		String lastname;
		Date birthdate;
		List<Data> dataItems;
	}
	
	class Data
	{
		String dataItem;
	}
	// domain model definition - end

	// processor for domain model information
	private class Task extends AbstractTypeElementsTaskTypes
	{
		// constructor
		private Task(Set<? extends Class<?>> types) { super(types); }

		// process model domain information
		@Override public boolean execute() throws TaskException
		{
			Map<TypeElement, String> typeToTargetTypeName = new HashMap<>();

			String personTargetTypeName =
					Person.class.getPackage().getName() + "." + Person.class.getSimpleName();
			String dataTargetTypeName =
					Data.class.getPackage().getName() + "." + Data.class.getSimpleName();

			typeToTargetTypeName.put(getTypeElement(Person.class), personTargetTypeName);
			typeToTargetTypeName.put(getTypeElement(Data.class), dataTargetTypeName);

			// iterate types of domain model
			for (TypeElement type : getTypeElements())
			{
				// extract type's package and name
				String packagename = getPackageQualifiedName(type);
				String typename = type.getSimpleName().toString();

				// make backup of current import manager
				ImportManager backup = newImportManager(packagename, typename, typeToTargetTypeName);

				// create and configure a generator for the Java Bean to be generated
				ClassGenerator cg = new DefaultClassGenerator();

				cg.setPackageName(packagename);
				cg.setVisibility(PUBLIC);
				cg.setClassName(type.getSimpleName());

				// iterate the fields of the domain model type
				for (VariableElement field : getFields(type))
				{
					String fieldname = field.getSimpleName().toString();

					// create and configure a generator for the field declaration
					FieldGenerator fg = new DefaultInstanceFieldGenerator();

					fg.setVisibility(PRIVATE);
					fg.setTypeFrom(field);
					fg.setName(fieldname);
					fg.setLineIndenter(TAB_SINGLE);

					// add field generator to class generator
					cg.addFieldGenerator(fg);

					// create and configure a generator for the field getter method
					MethodGenerator mgg = new DefaultInstanceMethodGenerator();

					mgg.setVisibility(PUBLIC);
					mgg.setTypeFrom(field);
					mgg.setName("get" + firstLetterToUpperCase(fieldname));
					mgg.setMethodBody("return " + fieldname + ";");
					mgg.setLineIndenter(TAB_SINGLE);
					
					// add getter method generator to class generator
					cg.addMethodGenerator(mgg);

					// create and configure a generator for the field setter method
					MethodGenerator mgs = new DefaultInstanceMethodGenerator();

					mgs.setVisibility(PUBLIC);
					mgs.setName("set" + firstLetterToUpperCase(fieldname));
					mgs.addParameterFrom(field, field.getSimpleName());
					mgs.setMethodBody("this." + fieldname + " = " + fieldname + ";");
					mgs.setLineIndenter(TAB_SINGLE);
					
					// add setter method generator to class generator
					cg.addMethodGenerator(mgs);
				}

				// add generator for import statements to class generator
				cg.addImportStatementGenerators(
						getImportManager().getImportStatementGenerators());

				try
        {
					FileUtils.writeStringToFile(
							CompilationUnitUtil.buildFile("src/gen/java", packagename, typename),
							cg.generate().toString());
        }
        catch (GeneratorException | IOException e)
        {
        	throw new TaskException("failure generating class for " + type, e);
        }
				finally
				{
					// restore formally import manager
					restoreImportManager(backup);
				}
			}

			return false;
		}
	}

	@Test
	public void test() throws IOException
	{
		Task task = new Task(asSet(Person.class, Data.class));
		TaskRunner.run(task);
	}
}